package server

import (
  "crypto/md5"
  "fmt"
  "regexp"
  "strings"

  "github.com/siddontang/go-log/log"
)

const (
  ecBadParams RegisterError = iota
  ecBadUserName
  ecBadEmail
  ecUserExist
  ecEmailExist
)

var (
  allErrCodes = map[RegisterError]string{
    ecBadParams:   "bad params",
    ecBadUserName: "bad username format",
    ecBadEmail:    "bad email format",
    ecUserExist:   "username exists",
    ecEmailExist:  "email exists",
  }
)

type RegisterError uint8

func (er RegisterError) Error() string {
  if s, ok := allErrCodes[er]; ok {
    return s
  }
  return "unknown error"
}

// email validator
// forker from https://github.com/ungerik/go-mail/blob/master/address.go
var emailRegexp *regexp.Regexp = regexp.MustCompile(`[a-zA-Z0-9\-+~_%]+[a-zA-Z0-9\-+~_%.]*@([a-z0-9]+[a-z0-9\-]*\.)+[a-z][a-z]+`)

// normalizeAddress trims space and converts the domain to lower case.
func normalizeAddress(address string) string {
  address = strings.TrimSpace(address)
  i := strings.Index(address, "@")
  if i == -1 {
    return address
  }
  return address[:i+1] + strings.ToLower(address[i+1:])
}

// validateAddress uses a simplified regular expression for checking
// email addresses. Please report real world addresses that are not working as bug.
func validateAddress(address string) bool {
  address = normalizeAddress(address)
  return emailRegexp.Match([]byte(address))
}

func checkRegisterParams(username, familyname, email, password string) error {
  if username == "" || familyname == "" || email == "" || password == "" {
    return ecBadParams
  }

  // check email format
  if !validateAddress(email) {
    return ecBadEmail
  }
  return nil
}

func memberRegisterCommand(c *requestContext) error {
  var (
    username   = string(c.args[1])
    familyname = string(c.args[2])
    email      = string(c.args[3])
    password   = string(c.args[4])
  )

  log.Info("UserName: %s, Password: %s, FamilyName: %s, Email: %s",
    username, password, familyname, email)

  err := checkRegisterParams(username, familyname, email, password)
  if err != nil {
    c.resp.writeError(err)
    return err
  }

  existed, err := c.app.dbIsUserExist(username)
  if err != nil {
    // TODO
    // return what to client??
    return err
  }
  if existed {
    c.resp.writeError(ecUserExist)
    return ecUserExist
  }

  existed, err = c.app.dbIsEmailExist(email)
  if err != nil {
    // TODO
    // return what to client??
    return err
  }
  if existed {
    c.resp.writeError(ecEmailExist)
    return ecEmailExist
  }

  c.family = dflFamilyMgr.GetFamilyByHostName(familyname)
  if c.family == nil {
    c.family, _ = dflFamilyMgr.NewFamily(familyname)
    // calculate md5 of email
    c.family.uuid = fmt.Sprintf("%x", md5.Sum([]byte(email)))
  }
  c.nodeinfo = c.family.GetMember(username)
  if c.nodeinfo == nil {
    c.nodeinfo, err = c.family.InsertMember(c.app.db, username, password)
    if err != nil {
      // TODO
      // return what to client??
      return err
    }
  }

  c.resp.writeBulk(respOK)
  return nil
}

func memberCommand(c *requestContext) (err error) {
  if len(c.args) != 5 {
    return ErrCmdParams
  }

  subcmd := string(c.args[0])
  switch subcmd {
  case "register.json":
    err = memberRegisterCommand(c)
  default:
    err = ErrCmdParams
  }
  return
}

func init() {
  register("member", memberCommand)
}
